package IC.LIR;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import IC.BinaryOps;
import IC.AST.*;
import IC.LIR.ClassLayout;
import IC.Semantics.SemanticChecker;
import IC.Semantics.SemanticError;
import IC.Semantics.symbols.GlobalSymbolTable;
import IC.Semantics.symbols.MethodSymbol;
import IC.Semantics.symbols.FieldSymbol;
import IC.Semantics.symbols.BlockSymbolTable;
import IC.Semantics.types.ClassType;
import IC.Semantics.types.SemanticType;
import IC.Semantics.types.TypeTable;


public class SimpleTranslationVisitor implements TranslationVisitor {

	//Symbol table for symbol information
	private GlobalSymbolTable globalSymbolTable;
	//List of String Literals
	private Map<String,String> stringLiterals = new TreeMap<String,String>();
	//List of global labels
	//List of class layouts in the program
	private Map<String,ClassLayout> classLayouts = new HashMap<String,ClassLayout>();
	//Count the If Label number to produce unique labels
	private int labelCount = 0;
	//Count the while labels to produce unique labels
	private int CurrWhileLabelNum = 0;
	//Count the literals in the program
	private int literalCount = 0;
	//main method
	private String mainMethod = "";
	//list of methods
	private List<String> methodList = new ArrayList<String>();
	//current class name
	private String currClassName = "";
	
	private String runtimeErrorHandlers = 
			"#Runtime Error Handlers:\n" +
			
			"__checkNullRef:\n" +
	        "Move ref,Rc1\n" +
	        "Compare 0,Rc1\n" +
	        "JumpTrue __checkNullRef_err\n" +
	        "Return 9999\n" +
	        "__checkNullRef_err:\n" +
	        "Library __println(strNull),Rdummy\n" +
	        "Jump _error_exit\n\n" +
	                
	        "__checkArrayAccess:\n" +
	        "Move arr,Rc1\n" +
	        "Move index,Rc2\n" +
	        "ArrayLength Rc1,Rc1\n" +
	        "Compare Rc1,Rc2\n" +
	        "JumpGE __checkArrayAccess_err\n" +
	        "Compare 0,Rc2\n" +
	        "JumpL __checkArrayAccess_err\n" +
	        "Return 9999\n" +
	        "__checkArrayAccess_err:\n" +
	        "Library __println(strIndex),Rdummy\n" +
	        "Jump _error_exit\n\n" +
	                
	        "__checkSize:\n" +
	        "Move size,Rc1\n" +
	        "Compare 0,Rc1\n" +
	        "JumpL __checkSize_err\n" +
	        "Return 9999\n" +
	        "__checkSize_err:\n" +
	        "Library __println(strNegSize),Rdummy\n" +
	        "Jump _error_exit\n\n" +
	                
	        "__checkZero:\n" +
	        "Move num,Rc1\n" +
	        "Compare 0,Rc1\n" +
	        "JumpTrue __checkZero_err\n" +
	        "Return 9999\n" +
	        "__checkZero_err:\n" +
	        "Library __println(strDivZero),Rdummy\n" +
	        "Jump _error_exit\n\n";
			
	
	public SimpleTranslationVisitor(GlobalSymbolTable globalSymbolTable) {
		this.globalSymbolTable = globalSymbolTable;
	}
	
	@Override
	public LIRTranslation visit(Program program, Integer next) {
		//Build class layouts for all user defined classes
		for (ICClass icClass : program.getClasses()) {
			if (icClass.getName().equals("Library")) continue;
			ClassLayout classLayout;
			if (icClass.hasSuperClass()) {
				classLayout = new ClassLayout(icClass, classLayouts.get(icClass.getSuperClassName()));
			}
			else {
				classLayout = new ClassLayout(icClass);
			}
			//insert the classLayout to the class layout table
			classLayouts.put(icClass.getName(), classLayout);
		}
		
		//Add string literals for runtime errors
		stringLiterals.put("\"Runtime Error: Null pointer dereference!\"", "strNull");
		stringLiterals.put("\"Runtime Error: Array index out of bounds!\"", "strIndex");
		stringLiterals.put("\"Runtime Error: Array allocation with negative array size!\"","strNegSize");
		stringLiterals.put("\"Runtime Error: Division by zero!\"","strDivZero");
		
		//go over all classes in the program and convert to lir
		for (ICClass icClass : program.getClasses()) {
			if (icClass.getName().equals("Library")) continue;
			icClass.accept(this,0);
		}
		
		//build the program lir code
		StringBuilder lirBuffer = new StringBuilder("");
		
		//add program title to lir code
		lirBuffer.append("#LIR code for: "+globalSymbolTable.getName()+"\n");
		
		//put code for string literals
		lirBuffer.append("#String Literals in the program:\n");
		for (String literal : stringLiterals.keySet()) {
			lirBuffer.append(stringLiterals.get(literal)+": ");
			lirBuffer.append(literal + "\n");
		}
		
		//put class dispatch tables
		lirBuffer.append("#Class Dispatch tables:\n");
		for (String classLayout : classLayouts.keySet()) {
			lirBuffer.append(classLayouts.get(classLayout).getDispatchTable());
		}
		lirBuffer.append("\n");
		
		//put code for methods
		lirBuffer.append("#Methods\n");
		
		//put runtime error handlers
		lirBuffer.append(runtimeErrorHandlers);
		//Put main method code
		lirBuffer.append(mainMethod);
		//Put all other methods
		for (String method : methodList) {
			lirBuffer.append(method);
		}
		
		lirBuffer.append("\n");
		
		//Add exit label for runtime error exit
		lirBuffer.append("_error_exit:\n");
		
		return new LIRTranslation(lirBuffer.toString(),"",LIRInstruction.OTHER);
	}

	@Override
	public LIRTranslation visit(ICClass icClass, Integer next) {
		currClassName = icClass.getName();
		for (Method method : icClass.getMethods()) {
			method.accept(this,0);
		}
		return new LIRTranslation("","",LIRInstruction.OTHER);
	}

	@Override
	public LIRTranslation visit(Field field, Integer next) {
		// never called
		return new LIRTranslation("","",LIRInstruction.OTHER);
	}

	@Override
	public LIRTranslation visit(VirtualMethod method, Integer next) {
		TranslateMethod(method,next,false);
		return new LIRTranslation("","",LIRInstruction.OTHER);
	}
	
	private void TranslateMethod(Method method, int targetReg, boolean isMainMethod) {
		String methodCode = "";
		//add method label
		if (isMainMethod) {
			methodCode += "# main in " + method.getEnclosingScope().getName() + "\n";
			methodCode += "_ic_main:\n";
		} else {
			methodCode += "_" + method.getEnclosingScope().getName();
			methodCode += "_" + method.getName() +":\n";
		}
		//add method statements
		for (Statement statement : method.getStatements()) {
			methodCode += statement.accept(this, targetReg).getCode();
		}
		if ((method.getType().getName().equals("void"))&&(!isMainMethod)) {
			methodCode += "Return 9999\n";
		}
		if (isMainMethod) {
			mainMethod = methodCode;
		} else {
			methodList.add(methodCode);
		}
	}

	@Override
	public LIRTranslation visit(StaticMethod method, Integer next) {
		boolean isMainMethod = method.getName().equals("main");
		TranslateMethod(method, next, isMainMethod);
		return new LIRTranslation("","",LIRInstruction.OTHER);
	}

	@Override
	public LIRTranslation visit(LibraryMethod method, Integer next) {
		return new LIRTranslation("","",LIRInstruction.OTHER);
	}

	@Override
	public LIRTranslation visit(Formal formal, Integer next) {
		return new LIRTranslation("","",LIRInstruction.OTHER);
	}

	@Override
	public LIRTranslation visit(PrimitiveType type, Integer next) {
		return new LIRTranslation("","",LIRInstruction.OTHER);
	}

	@Override
	public LIRTranslation visit(UserType type, Integer next) {
		return new LIRTranslation("","",LIRInstruction.OTHER);
	}

	@Override
	public LIRTranslation visit(Assignment assignment, Integer next) {
		//TODO: changed assignment available reg next->next+1, variable available reg next+1->next 
		//Translate assignment right hand expression
		LIRTranslation assignmentExpression = assignment.getAssignment().accept(this, next);
		String lirCode = assignmentExpression.getCode();
		lirCode += getMoveInstruction(assignmentExpression.getInstructionType());
		lirCode += assignmentExpression.getTargetRegister() + ",R" + next + "\n";
		
		//Translate Variable location
		LIRTranslation variableTranslation = assignment.getVariable().accept(this, next+1);
		lirCode += variableTranslation.getCode();
		
		//Make the assignment
		lirCode += getMoveInstruction(variableTranslation.getInstructionType());
		lirCode += "R" + next + "," + variableTranslation.getTargetRegister() + "\n";
		
		return new LIRTranslation(lirCode,"", LIRInstruction.STATEMENT);
	}

	@Override
	public LIRTranslation visit(CallStatement callStatement, Integer next) {
		return callStatement.getCall().accept(this, next);
	}

	@Override
	public LIRTranslation visit(Return returnStatement, Integer next) {
		String Translation = "";
		if (returnStatement.hasValue()) {
			LIRTranslation returnExpression = returnStatement.getValue().accept(this, next);
			Translation += returnExpression.getCode();
			Translation += "Return "+returnExpression.getTargetRegister()+"\n";
		} else {
			Translation += "Return 9999\n";
		}
		return new LIRTranslation(Translation, "", LIRInstruction.RETURN);
	}

	@Override
	public LIRTranslation visit(If ifStatement, Integer next) {
		//Generate unique labels
		String falseLabel = "_false_label" + labelCount;
		String endLabel = "_end_label" + labelCount;
		labelCount++;
		
		//Get condition calculation code
		LIRTranslation conditionExp = ifStatement.getCondition().accept(this, next);
		
		//decide jump if false address based on whether else clause exist
		String elseLabel = ifStatement.hasElse() ? falseLabel : endLabel;
		String ifCode = conditionExp.getCode();
		ifCode += getMoveInstruction(conditionExp.getInstructionType());
		ifCode += conditionExp.getTargetRegister() + ",R" + next + "\n";
		ifCode += "Compare 0,R" + next + "\n";
		ifCode += "JumpTrue " + elseLabel + "\n";
		ifCode += ifStatement.getOperation().accept(this, next).getCode();
		
		//End or else clause
		if (ifStatement.hasElse()) {
			ifCode += "Jump " + endLabel + "\n";
			ifCode += falseLabel + ":\n";
			ifCode += ifStatement.getElseOperation().accept(this, next).getCode();
		}
		ifCode += endLabel + ":\n";
		return new LIRTranslation(ifCode,"",LIRInstruction.STATEMENT);
	}

	@Override
	public LIRTranslation visit(While whileStatement, Integer next) {
		//assign unique jump labels
		String testLabel = "_test_label" + labelCount;
		String endLabel = "_end_label" + labelCount;
		labelCount++;
		//save label of current scope - for break/continue 
		int lastWhileLabel = CurrWhileLabelNum;
		CurrWhileLabelNum = labelCount;
		String whileCode = testLabel + ":\n";
		
		//Condition Check
		LIRTranslation whileCondition = whileStatement.getCondition().accept(this, next);
		whileCode += whileCondition.getCode();
		whileCode += getMoveInstruction(whileCondition.getInstructionType());
		whileCode += whileCondition.getTargetRegister() + ",R" + next +"\n";
		whileCode += "Compare 0,R" + next + "\n";
		
		//Loop Body
		whileCode += "JumpTrue " + endLabel + "\n";
		whileCode += whileStatement.getOperation().accept(this, next).getCode();
		whileCode += "Jump " + testLabel + "\n";
		
		//Exit Loop
		whileCode += endLabel + ":\n";
		
		//Restore last while label count
		CurrWhileLabelNum = lastWhileLabel;
		
		return new LIRTranslation(whileCode, "", LIRInstruction.STATEMENT);
	}

	@Override
	public LIRTranslation visit(Break breakStatement, Integer next) {
		String Translation = "Jump _end_label" + CurrWhileLabelNum + "\n";
		return new LIRTranslation(Translation,"",LIRInstruction.STATEMENT);
	}

	@Override
	public LIRTranslation visit(Continue continueStatement, Integer next) {
		String Translation = "Jump _test_label" + CurrWhileLabelNum + "\n";
		return new LIRTranslation(Translation,"",LIRInstruction.STATEMENT);
	}

	@Override
	public LIRTranslation visit(StatementsBlock statementsBlock, Integer next) {
		String Translation = "";
		for (Statement stmt : statementsBlock.getStatements()) {
			Translation += stmt.accept(this, next).getCode();
		}
		return new LIRTranslation(Translation,"",LIRInstruction.STATEMENT);
	}

	@Override
	public LIRTranslation visit(LocalVariable localVariable, Integer next) {
		String Translation = "";
		//Check if the local variable has an initial value
		if (localVariable.hasInitValue()) {
			LIRTranslation initValue = localVariable.getInitValue().accept(this, next);
			Translation += initValue.getCode();
			//Move the value to a register
			//(not all move instructions has direct access to memory) 
			if (initValue.getInstructionType() == LIRInstruction.LITERAL) {
				Translation += "Move " + initValue.getTargetRegister() + "," + localVariable.getName() + "\n";
			} else {
				Translation += getMoveInstruction(initValue.getInstructionType());
				Translation += initValue.getTargetRegister() + ",R" + next + "\n";
				//Move the value in the register to the variable in memory
				Translation += "Move R" + next + "," + localVariable.getName() + "\n";
			}	
		}
		return new LIRTranslation(Translation,"",LIRInstruction.STATEMENT);
	}

	@Override
	public LIRTranslation visit(VariableLocation location, Integer next) {
		String Translation = "";
		if (location.isExternal()) {
			//Translate location
			LIRTranslation TranslatedLocation = location.getLocation().accept(this, next);
			Translation += TranslatedLocation.getCode();
			//Get Class layout of the variable location
			String className = ((ClassType)location.getLocation().accept(new SemanticChecker(globalSymbolTable.getName()))).getName();
			ClassLayout classLayout = classLayouts.get(className);
			//Get the field offset
			int offset = classLayout.getFieldOffset(classLayout.getFieldFromName(location.getName()));
			
			//Move the object to a register
			Translation += getMoveInstruction(TranslatedLocation.getInstructionType());
			Translation += TranslatedLocation.getTargetRegister() + ",R" + next + "\n";
			
			 // Null reference check of external location
			Translation += "StaticCall __checkNullRef(ref=R"+next+"),Rdummy\n";
			
			return new LIRTranslation(Translation,"R" + next + "." + offset, LIRInstruction.FIELD_ACCESS);
		} else {
			//This is a local location
			//we want to check if this is a local variable or a field
			if ((((BlockSymbolTable)location.getEnclosingScope()).lookup(location.getName())) instanceof FieldSymbol) {
				//Get the enclosing class
				String className = ((BlockSymbolTable)location.getEnclosingScope()).getClassScope().getName();
				ClassLayout classLayout = classLayouts.get(className);
				//Get the field offset
				Field field = classLayout.getFieldFromName(location.getName());
				int offset = classLayout.getFieldOffset(field);
				
				Translation += "Move this,R" + next + "\n";
				return new LIRTranslation(Translation, "R" + next + "." + offset, LIRInstruction.FIELD_ACCESS);
			} else {
				//This is a local variable
				return new LIRTranslation("",location.getName(),LIRInstruction.REGISTER_OP);
			}
		}
	}

	@Override
	public LIRTranslation visit(ArrayLocation location, Integer next) {
		String Translation = "";
		//Translating array address
		LIRTranslation arrayTrans = location.getArray().accept(this, next);
		Translation += arrayTrans.getCode();
		Translation += getMoveInstruction(arrayTrans.getInstructionType())
					   +arrayTrans.getTargetRegister()+",R"+next.toString()+"\n";
		
		// Handle array null reference
        Translation += "StaticCall __checkNullRef(ref=R"+next+"),Rdummy\n";
		
		//Translating array index expression
		LIRTranslation indexTrans = location.getIndex().accept(this,next+1);
		Translation += indexTrans.getCode();
		Translation += getMoveInstruction(indexTrans.getInstructionType())
				       +indexTrans.getTargetRegister()+",R"+(next+1)+"\n";
		
		// check for array index out of bounds error
        Translation += "StaticCall __checkArrayAccess(arr=R"+next+",index=R"+(next+1)+"),Rdummy\n";
		
		return new LIRTranslation(Translation,"R"+next+"["+"R"+(next+1)+"]",LIRInstruction.ARRAY_ACCESS);
	}

	@Override
	public LIRTranslation visit(StaticCall call, Integer next) {
		String Translation ="";
		//String returnRegister = (call.getName())
		int regsOffset = 0;
		for (Expression arg : call.getArguments()) {
			LIRTranslation currArgumentTranslation = arg.accept(this, next + regsOffset);
			Translation += "#Argument " + (regsOffset+1) + " Translation:\n";
			Translation += currArgumentTranslation.getCode();
			Translation += getMoveInstruction(currArgumentTranslation.getInstructionType());
			Translation += currArgumentTranslation.getTargetRegister() + ",R" + (next+regsOffset) + "\n";
			regsOffset++;
		}
		
		//Check if this is a library function call
		if (call.getClassName().equals("Library")) {
			return HandleLibraryCall(Translation,call,next);
		}
		
		Translation += "StaticCall " + call.getClassName() + "_" + call.getName() + "(";
		
		//Translate <Formal>=<Actual> argument pairs
		ClassLayout currClassLayout = classLayouts.get(call.getClassName());
		if (currClassLayout != null) {
			Method currMethod = currClassLayout.getMethodFromName(call.getName());
			if (currMethod != null) {
				for (int i = 0; i < call.getArguments().size(); i++) {
					Translation += currMethod.getFormals().get(i).getName() + "=" + "R" + (next+i);
					if ((i+1) != call.getArguments().size()) {
						Translation += ",";
					}
				}
				Translation += "),R";
				if (((MethodSymbol)currMethod.getEnclosingScope().lookup(call.getName())).getType().getReturnType().getName() == "void") {
					Translation += "dummy\n";
					return new LIRTranslation(Translation,"",LIRInstruction.METHOD_CALL);
				} else {
					Translation += next + "\n";
					return new LIRTranslation(Translation,"R" + next,LIRInstruction.REGISTER_OP);
				}			
			} else {
				System.out.println("ERROR: LIR Translatior can not find function");
			}
		} else {
			System.out.println("ERROR: LIR Translatior can not find class layout");
		}
		return new LIRTranslation(Translation,"",LIRInstruction.OTHER);
	}
	
	/**
	 * Handles Library function calls, library functions have special syntax in LIR.
	 * The call does not contain the formal parameters
	 * @param argsTrans
	 * 					The translation so far - argument evaluation
	 * @param call
	 * 					The function call object
	 * @param next
	 * 					the first available register
	 * @return
	 * 					LIRTranslation object contains the call translation
	 */
	public LIRTranslation HandleLibraryCall(String argsTrans, StaticCall call, int next) {
		
		String Translation = argsTrans;
		Translation += "Library __" + call.getName() + "(";
		for (int i = 0; i < call.getArguments().size(); i++) {
			Translation += "R" + (next+i);
			if ((i+1) != call.getArguments().size()) {
				Translation += ",";
			}
		}
		Translation += "),R" + next +"\n";
		return new LIRTranslation(Translation,"R"+next, LIRInstruction.REGISTER_OP);
	}

	@Override
	public LIRTranslation visit(VirtualCall call, Integer next) {
		String Translation ="";
		
		//Translate the object location
		if (call.isExternal()) {
			LIRTranslation objectTranslation = call.getLocation().accept(this, next);
			Translation += objectTranslation.getCode();
			
			//TODO: changed move instruction
			Translation += getMoveInstruction(objectTranslation.getInstructionType());
			Translation += objectTranslation.getTargetRegister() + ",R" + next + "\n";
			
            // check if an object location is a null reference
            Translation += "StaticCall __checkNullRef(ref=R"+next+"),Rdummy\n";
            
		} else {
			Translation += "Move this,R" + next +"\n";
		}
		
		//Translate the call's arguments
		int regsOffset = 1;
		for (Expression arg : call.getArguments()) {
			LIRTranslation currArgumentTranslation = arg.accept(this, next + regsOffset);
			Translation += "#Argument " + (regsOffset) + " Translation:\n";
			Translation += currArgumentTranslation.getCode();
			Translation += getMoveInstruction(currArgumentTranslation.getInstructionType());
			Translation += currArgumentTranslation.getTargetRegister() + ",R" + (next+regsOffset) + "\n";
			regsOffset++;
		}
		
		//Function Call
		Translation += "VirtualCall R" + next + ".";
		
		//Get class name from location
		String className = "";
		if (call.isExternal()) {
			className = ((ClassType)call.getLocation().accept(new SemanticChecker(globalSymbolTable.getName()))).getName();
		} else {
			className = currClassName;
		}
		
		//Get method offset from class layout
		ClassLayout classLayout = classLayouts.get(className);
		Method method = classLayout.getMethodFromName(call.getName());
		int offset = classLayout.getMethodOffset(method);
		Translation += offset + "(";
		
		//Translate <Formal>=<Actual> argument pairs
		for (int i = 0; i < call.getArguments().size(); i++) {
			Translation += method.getFormals().get(i).getName() + "=" + "R" + (next+i+1);
			if ((i+1) != call.getArguments().size()) {
				Translation += ",";
			}
		}
		
		//Add the return register
		Translation += "),R";
		if (((MethodSymbol)method.getEnclosingScope().lookup(call.getName())).getType().getReturnType().getName() == "void") {
			Translation += "dummy\n";
			return new LIRTranslation(Translation,"",LIRInstruction.METHOD_CALL);
		} else {
			Translation += next + "\n";
			return new LIRTranslation(Translation,"R" + next,LIRInstruction.REGISTER_OP);
		}			
	}

	@Override
	public LIRTranslation visit(This thisExpression, Integer next) {
		String translation = "Move this,R" + next + "\n";
		return new LIRTranslation(translation, "R"+next.toString(), LIRInstruction.REGISTER_OP);
	}

	@Override
	public LIRTranslation visit(NewClass newClass, Integer next) {
		ClassLayout classLayout = classLayouts.get(newClass.getName());
		int objectSize = (classLayout.getNumFields()+1)*4;
		String Translation ="";
		Translation += "Library __allocateObject("+objectSize+"),R"+next+"\n";
		Translation += getMoveInstruction(LIRInstruction.FIELD_ACCESS)+
				       "_DV_"+classLayout.getClassName()+",R"+next+".0\n";
		return new LIRTranslation(Translation,"R"+next.toString(),LIRInstruction.REGISTER_OP);
	}

	@Override
	public LIRTranslation visit(NewArray newArray, Integer next) {
		LIRTranslation arraySize = newArray.getSize().accept(this, next);
		String Translation = arraySize.getCode();
		Translation += getMoveInstruction(arraySize.getInstructionType());
		Translation += arraySize.getTargetRegister()+",R"+next+"\n";
		Translation += "Mul 4,R"+next+"\n";
		
		// check if the given array size is not negative
        Translation += "StaticCall __checkSize(size=R"+next+"),Rdummy\n";
        
		
		Translation += "Library __allocateArray(R"+next+"),R"+next+"\n";
		return new LIRTranslation(Translation,"R"+next.toString(),LIRInstruction.REGISTER_OP);
	}

	@Override
	public LIRTranslation visit(Length length, Integer next) {
		String Translation = "";
		LIRTranslation arrayExpression = length.getArray().accept(this, next);
		Translation += arrayExpression.getCode();
		Translation += getMoveInstruction(arrayExpression.getInstructionType());
		//Move array reference to a target register
		Translation += arrayExpression.getTargetRegister() + ",R" + next + "\n";
		//Check the array reference is not a null reference
		Translation += "StaticCall __checkNullRef(ref=R"+next+"),Rdummy\n";
		
		//get array length
		Translation += "ArrayLength R" + next + ",R" + next + "\n";
		return new LIRTranslation(Translation,"R"+next.toString(),LIRInstruction.REGISTER_OP);
	}

	@Override
	/**
	 * Translation visitor for mathematical Binary Operations
	 * the operations are: Plus, Minus, Multiply, Divide, Modulus
	 * 
	 * @param binaryOp
	 * 					the mathematical operation
	 * @param next
	 * 					the available register number
	 */
	public LIRTranslation visit(MathBinaryOp binaryOp, Integer next) {
		String Translation = "";
		
		//Evaluate first operand
		LIRTranslation operand1 = binaryOp.getFirstOperand().accept(this, next);
		Translation += operand1.getCode();
		Translation += getMoveInstruction(operand1.getInstructionType());
		Translation += operand1.getTargetRegister() + ",R" + next + "\n";
				
	    //Evaluate second operand
		LIRTranslation operand2 = binaryOp.getSecondOperand().accept(this, next+1);
		Translation += operand2.getCode();
		Translation += getMoveInstruction(operand2.getInstructionType());
		Translation += operand2.getTargetRegister() + ",R" + (next+1) + "\n";
		
		//Apply operator
		switch(binaryOp.getOperator()) {
			case PLUS: { 
				//Check if the operation is done on integers or strings
				SemanticType opType = (SemanticType)binaryOp.getFirstOperand().accept(new SemanticChecker(globalSymbolTable.getName()));
				try {
					//If this is integer addition
					if (opType.subtypeOf(TypeTable.getPrimitiveType(SemanticType.INT_TYPE_NAME))) {
						 Translation += "Add R" + (next+1) +",R" + next + "\n";
					} else {
						//If this is a string concatenation
						Translation += "Library __stringCat(R" + next + ",R" + (next+1) + "),R" + next + "\n";
					}
				} catch(SemanticError e)
				{
					System.err.println(e.getMessage());
					e.printStackTrace();
				}
				break;
			}
			case MINUS: { 
				Translation += "Sub R" + (next+1) +",R" + next + "\n";
				break;
			}
			case MULTIPLY: { 
				Translation += "Mul R" + (next+1) +",R" + next + "\n";
				break;
			}
			case DIVIDE: {
                // check division by zero
                Translation += "StaticCall __checkZero(num=R"+(next+1)+"),Rdummy\n";
				Translation += "Div R" + (next+1) +",R" + next + "\n";
				break;
			}
			case MOD: { 
				// check division by zero
                Translation += "StaticCall __checkZero(num=R"+(next+1)+"),Rdummy\n";
				Translation += "Mod R" + (next+1) +",R" + next + "\n";
				break;
			}
			default:
				System.out.println("Error: LIR - Mathematical Binary Op");
		}
		return new LIRTranslation(Translation, "R" + next, LIRInstruction.BINARY);
	}

	@Override
	/**
	 * Translation visitor for logical Binary Operations
	 * the operations are: logical OR, logical AND,
	 * Lower Than, Lower Than or Equal to, Greater Than, Greater Than or Equal to,
	 * Equal, Not Equal.
	 * 
	 * @param binaryOp
	 * 					the logical operation
	 * @param next
	 * 					the available register number
	 */
	public LIRTranslation visit(LogicalBinaryOp binaryOp, Integer next) {
		String Translation = "";
		
		//Generate labels
		String trueLabel = "_true_label" + labelCount;
		String endLabel = "_end_label" + labelCount++;
		
		//Evaluate first operand
		LIRTranslation operand1 = binaryOp.getFirstOperand().accept(this, next);
		Translation += operand1.getCode();
		Translation += getMoveInstruction(operand1.getInstructionType());
		Translation += operand1.getTargetRegister() + ",R" + next + "\n";
		
		//Evaluate second operand
		LIRTranslation operand2 = binaryOp.getSecondOperand().accept(this, next+1);
		String operand2Trans = operand2.getCode();
		operand2Trans += getMoveInstruction(operand2.getInstructionType());
		operand2Trans += operand2.getTargetRegister() + ",R" + (next+1) + "\n";
		
		//act differently to OR, AND from other logical operations.
		//evaluate second operand only when necessary.
		if ((binaryOp.getOperator().compareTo(BinaryOps.LAND) == 0)||
			(binaryOp.getOperator().compareTo(BinaryOps.LOR) == 0)) {
			//variables for case differences
			String logicalOp = "";
			String compareWith = "";
			switch(binaryOp.getOperator()) {
				case LOR: {
					logicalOp = "Or ";
					compareWith = "1";
					break;
				}
				case LAND: {
					logicalOp = "And ";
					compareWith = "0";
					break;
				}
				default:
					System.out.println("Error: LIR - Logical Binary Op (AND,OR)");
			}
			Translation += "Compare " + compareWith + ",R" + next + "\n";
			Translation += "JumpTrue " + endLabel + "\n";
			//Inject operand2 translation - only when needed
			Translation += operand2Trans;
			Translation += logicalOp + "R" + (next+1) + ",R" + next + "\n";
		}
		//If logical operator is not OR or AND
		else {
			Translation += operand2Trans;
			//compare operands
			Translation += "Compare R" + (next) + ",R" + (next+1) + "\n";
			switch(binaryOp.getOperator()) {
				case LT: {
					Translation += "JumpL ";
					break;
				}
				case LTE: {
					Translation += "JumpLE ";
					break;
				}
				case GT: {
					Translation += "JumpG ";
					break;
				}
				case GTE: {
					Translation += "JumpLE ";
					break;
				}
				case EQUAL: {
					Translation += "JumpTrue ";
					break;
				}
				case NEQUAL: {
					Translation += "JumpFalse ";
					break;
				}
				default:
					System.out.println("Error: LIR - Logical Binary Op (LT,LTE,GT,GTE,EQUAL,NEQUAL)");
			}
			Translation += trueLabel + "\n";
			Translation += "Move 0,R" + next + "\n";
			Translation += "Jump " + endLabel + "\n";
			Translation += trueLabel + ":\n";
			Translation += "Move 1,R" + next + "\n";
		}
		Translation += endLabel + ":\n";
		return new LIRTranslation(Translation, "R" + next, LIRInstruction.BINARY);
	}

	@Override
	/**
	 * Translation visitor for mathematical Unary Operations
	 * the only operation is unary subtraction.
	 * @param unaryOp
	 * 					the mathematical operation
	 * @param next
	 * 					the available register number
	 */
	public LIRTranslation visit(MathUnaryOp unaryOp, Integer next) {
		String Translation = "";
		
		//Evaluate operand
		LIRTranslation operand = unaryOp.getOperand().accept(this, next);
		Translation += operand.getCode();
		
		 //Move operand value to a register 
		 Translation += getMoveInstruction(operand.getInstructionType());
		 Translation += operand.getTargetRegister() + ",R" + next + "\n";
		 
		 //Perform mathematical operation
		 Translation += "Neg R" + next + "\n";
		
		 return new LIRTranslation(Translation, "R" + next, LIRInstruction.UNARY);
	}

	@Override
	/**
	 * Translation visitor for Logical Unary Operations
	 * the only operation is logical not
	 * @param unaryOp
	 * 					the logical operation
	 * @param next
	 * 					the available register number
	 */
	public LIRTranslation visit(LogicalUnaryOp unaryOp, Integer next) {
		 String Translation = "";
		 
		 //prepare labels
		 String trueLabel = "_true_label" + labelCount;
		 String endLabel = "_end_label" + labelCount++;
		 
		 //Evaluate operand
		 LIRTranslation operand = unaryOp.getOperand().accept(this, next);
		 Translation += operand.getCode();
		 
		 //Move operand value to a register 
		 Translation += getMoveInstruction(operand.getInstructionType());
		 Translation += operand.getTargetRegister() + ",R" + next + "\n";
		 
		 //Perform logical operator
		 Translation += "Compare 0,R" + next + "\n";
		 Translation += "JumpTrue " + trueLabel + "\n";
		 
		 //if value is != 0 - make it 0
		 Translation += "Move 0,R" + next + "\n";
		 Translation += "Jump " + endLabel + "\n";
		 
		 //if value is 0 - make it non-zero
		 Translation += trueLabel + ":\n";
		 Translation += "Move 1,R" + next + "\n";
		 Translation += endLabel + ":\n";
		 
		return new LIRTranslation(Translation, "R" + next, LIRInstruction.UNARY);
	}

	@Override
	/**
	 * Translation visitor for a literal
	 * translates the literal to a LIR immediate and not 
	 * to a register. string literals will be added to the general string literal list
	 * 
	 * @param literal
	 * 					the literal to translate
	 * @param next
	 * 					the available register number
	 */
	public LIRTranslation visit(Literal literal, Integer next) {
		String targetValue = "";
		switch(literal.getType()) {
			case INTEGER: {
				targetValue = literal.getValue().toString();
				break;
			}
			case STRING: {
				String stringValue = literal.getType().toFormattedString(literal.getValue());
				String stringName = "";
				if (!stringLiterals.containsKey(stringValue)) {
					stringName = "str" + literalCount++;
					stringLiterals.put(stringValue, stringName);
				} else {
					stringName = stringLiterals.get(stringValue);
				}
				targetValue = stringName;
				break;
			}
			case TRUE: {
				targetValue = "1";
				break;
			}
			case FALSE: {
				targetValue = "0";
			}
			case NULL: {
				targetValue = "0";
			}
		}
		return new LIRTranslation("",targetValue, LIRInstruction.LITERAL);
	}

	@Override
	public LIRTranslation visit(ExpressionBlock expressionBlock, Integer next) {
		return expressionBlock.getExpression().accept(this, next);
	}

	private String getMoveInstruction(LIRInstruction instructionType) {
		switch (instructionType) {
			case UNARY: return "Move ";
			case BINARY: return "Move ";
			case LITERAL: return "Move ";
			case REGISTER_OP: return "Move ";
			case ARRAY_ACCESS: return "MoveArray ";
			case FIELD_ACCESS: return "MoveField ";
			default: {
				System.err.print("Error: SimpleTranslationVisitor\n");
				return null;
			}
		}
	}	
}
